/*
 * Copyright (c) 2018-2024 北京华恒盛世科技有限公司
 * QUICKPOOL is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *     http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
*/

#include "subcmd.h"

static json *resourcemap_2json(struct QuickPoolResourceMap *, char *);
static void resourceinfo_usage(const char *);

static json *resourcedef_2json(struct resource_definition *);
static void clusterresource_usage(const char *);

/**
 * @brief       qp resource info handler, to query resource information from jm and js.
 *
 * @param[in]   argc          #1: number of input argument
 * @param[in]   argv          #2: string array of the input argument
 *
 * @retval      0             #1: succeed
 * @retval      1             #2: input option is wrong or kind of input argument not specified
 * @retval      2             #3: argument has syntax error
 * @retval      3             #4: failed to query resource information
 * @retval      4             #5: not enough memory to print json string
 */
int
cmd_resource_info(int argc, char **argv)
{
    bool_t staticResource=FALSE;
    while (TRUE) {
        int option_index = 0;
        static struct option long_options[] = {
            {"help", no_argument, 0, 'h'},
            {"static", no_argument, 0, 's'},
            {NULL, 0, 0, 0}
        };

        int opt;
        opt = getopt_long(argc, argv, "hs", long_options, &option_index);
        if (-1 == opt) {
            break;
        }

        switch (opt) {
        case 's':
            staticResource = TRUE;
            break;
        case 'h':
            resourceinfo_usage(argv[0]);
            exit(0);
        case '?':
        default:
            resourceinfo_usage(argv[0]);
            exit(1);
        }
    }

    struct quickpoolInfo *qpInfo;
    qpInfo = qp_info(NULL);
    if (NULL == qpInfo) {
        exit(3);
    }

    // if not specify resource name, set all to TRUE
    int all = FALSE;
    unsigned int numRes;
    char **resources=NULL, **resourcePoint;
    numRes = get_argument_names(argc, argv, optind, &resources, &all, "resource");
    if (0 == numRes || all) {
        resourcePoint = NULL;
    } else {
        resourcePoint = resources;
    }

    struct request resourceRequest;
    memset(&resourceRequest, 0, sizeof(struct request));

    if (0 < numRes && NULL != resourcePoint) {

        // to query specific resources.
        resourceRequest.number = 3;
        resourceRequest.keyvalues = (struct keyvalue *)calloc(3, sizeof(struct keyvalue));
        if (NULL == resourceRequest.keyvalues) {
            fprintf(stderr, "calloc(%ld) failed due to %m.\n",
                    resourceRequest.number*sizeof(struct keyvalue));
            exit(4);
        }

        resourceRequest.keyvalues[0].key = REQUEST_KEY_OPTIONS;
        resourceRequest.keyvalues[0].value = (char *)calloc(12, sizeof(char));
        if (NULL == resourceRequest.keyvalues[0].value) {
            fprintf(stderr, "calloc(%d) failed due to %m.\n", 12);
            exit(4);
        }
        snprintf(resourceRequest.keyvalues[0].value, 12, "%d", 0);

        resourceRequest.keyvalues[1].key = REQUEST_KEY_NUMBER;
        resourceRequest.keyvalues[1].value = (char *)calloc(12, sizeof(char));
        if (NULL == resourceRequest.keyvalues[1].value) {
            fprintf(stderr, "calloc(%d) failed due to %m.\n", 12);
            exit(4);
        }
        snprintf(resourceRequest.keyvalues[1].value, 12, "%d", numRes);

        resourceRequest.keyvalues[2].key = REQUEST_KEY_NAMES;
        unsigned int i, size = 0;
        for (i = 0; i < numRes; i++) {
            size += strlen(resourcePoint[i]) + 1;
        }
        resourceRequest.keyvalues[2].value = (char *)calloc(size+1, sizeof(char));
        if (NULL == resourceRequest.keyvalues[2].value) {
            fprintf(stderr, "calloc(%d) failed due to %m.\n", size);
            exit(4);
        }

        for (i = 0; i < numRes; i++) {
            if (i == 0) {
                strncpy(resourceRequest.keyvalues[2].value, resourcePoint[i], size);
            } else {
                strncat(resourceRequest.keyvalues[2].value, " ", size);
                strncat(resourceRequest.keyvalues[2].value, resourcePoint[i], size);
            }
        }
    } else {
        resourceRequest.number = 1;
        resourceRequest.keyvalues = (struct keyvalue *)calloc(1, sizeof(struct keyvalue));
        if (NULL == resourceRequest.keyvalues) {
            fprintf(stderr, "calloc(%ld) failed due to %m.\n",
                    resourceRequest.number*sizeof(struct keyvalue));
            exit(4);
        }

        resourceRequest.keyvalues[0].key = REQUEST_KEY_OPTIONS;
        resourceRequest.keyvalues[0].value = (char *)calloc(12, sizeof(char));
        if (NULL == resourceRequest.keyvalues[0].value) {
            fprintf(stderr, "calloc(%d) failed due to %m.\n", 12);
            exit(4);
        }
        snprintf(resourceRequest.keyvalues[0].value, 12, "%d", 0);
    }

    FREEUP(resources);

    quickpool_errno = 0;

    struct QuickPoolResourceReply *reply;
    if (staticResource) { // call JM to query static resource info

        reply = qp_sharedresourceinfo(&resourceRequest, NULL);
        if (quickpool_errno != 0 || NULL == reply) {

            if (NULL != reply) {
                // JM replied error message.
                fprintf(stderr, "%s\n", reply->error_message);
            } else {
                fprintf(stderr, "Get resource info failed due to above error.\n");
            }

            gf_xdr_free(gf_xdr_request, (void *)&resourceRequest);

            exit(3);
        }
    } else { // call JM to query dynamic resource info

        reply = client_get_resourceinfo(&resourceRequest, NULL);
        if (quickpool_errno != 0 || NULL == reply) {

            if (NULL != reply) {
                // JS replied error message.
                fprintf(stderr, "%s\n", reply->error_message);
            } else {
                fprintf(stderr, "Get resource info failed due to above error.\n");
            }

            gf_xdr_free(gf_xdr_request, (void *)&resourceRequest);
            exit(3);
        }
    }

    gf_xdr_free(gf_xdr_request, (void *)&resourceRequest);

    if (0 == reply->num_resources){

        if (staticResource) {
            fprintf(stderr, "No static shared resources defined in the quickpool cluster.\n");
        } else {
            fprintf(stderr, "No dynamic shared resources defined in the quickpool cluster.\n");
        }

        gf_xdr_free(libxdr_resource_reply, reply);
        FREEUP(reply);

        exit(0);
    }

    json *jsonResourceList = json_CreateObject();
    unsigned int i, j, k;
    for (k = 0; k < reply->num_resources; k++) {

        for (j = 0; j < qpInfo->num_resources; j++) {

            if (0 != strcmp(qpInfo->resource_table[j].name, reply->resources[k].resource_name)) {
                continue;
            }

            if ((staticResource && !(qpInfo->resource_table[j].flags & RESOURCE_FLAG_DYNAMIC))
                || (!staticResource && (qpInfo->resource_table[j].value_type & TYPE_NUMERIC))) {

                for (i = 0; i < reply->resources[k].num_instances; i++) {


                    json *jsonResource = resourcemap_2json(&(reply->resources[k].instances[i]),
                                                           reply->resources[k].resource_name);
                    json_AddItemToObject(jsonResourceList, reply->resources[k].resource_name,
                                         jsonResource);
                }
            }
        }
    }

    if (0 < json_GetArraySize(jsonResourceList)) {
        char *strJson;
        strJson = json_Print(jsonResourceList);
        if (NULL != strJson) {
            fprintf(stdout, "%s\n", strJson);
            FREEUP(strJson);
        } else {

            perror("malloc");
            json_Delete(jsonResourceList);
            gf_xdr_free(libxdr_resource_reply, reply);
            FREEUP(reply);

            exit(4);
        }
    } else {
        // nothing in json means there's no resource in the cluster, should not go here.
        fprintf(stderr, "No resource information in the cluster.\n");
    }
    json_Delete(jsonResourceList);

    gf_xdr_free(libxdr_resource_reply, reply);
    FREEUP(reply);

    exit(0);
} // end function cmd_resource_info

/**
 * @brief       qp cluster resource handler, to query cluster resource information from jm.
 *
 * @param[in]   argc          #1: number of input argument
 * @param[in]   argv          #2: string array of the input argument
 *
 * @retval      0             #1: succeed
 * @retval      1             #2: input option is wrong or kind of input argument not specified
 * @retval      3             #3: failed to query cluster resource information
 * @retval      4             #4: not enough memory to print json string
 */
int
cmd_list_resources(int argc, char **argv)
{
    bool_t rOpt=FALSE, tOpt=FALSE, mOpt=FALSE;
    while (TRUE) {
        int option_index = 0;
        static struct option long_options[] = {
            {"help", no_argument, 0, 'h'},
            {"resource", no_argument, 0, 'r'},
            {"model", no_argument, 0, 'm'},
            {"type", no_argument, 0, 't'},
            {NULL, 0, 0, 0}
        };

        int opt;
        opt = getopt_long(argc, argv, "hrmt", long_options, &option_index);
        if (-1 == opt) {
            break;
        }

        switch (opt) {
        case 'r':
            rOpt = TRUE;
            break;
        case 'm':
            mOpt = TRUE;
            break;
        case 't':
            tOpt = TRUE;
            break;
        case 'h':
            clusterresource_usage(argv[0]);
            exit(0);
        case '?':
        default:
            clusterresource_usage(argv[0]);
            exit(1);
        }
    }

    int numName, all = FALSE;
    char **names=NULL;
    numName = get_argument_names(argc, argv, optind, &names, &all, "queueC");

    struct quickpoolInfo *qpInfo;
    qpInfo = qp_info(NULL);
    if (NULL == qpInfo) {
        exit(-10);
    }

    if (!numName && !rOpt && !mOpt && !tOpt) {
        rOpt = mOpt = tOpt = TRUE;
    } else if (numName) {
        rOpt = TRUE;
    }

    unsigned int i;
    if (rOpt) { // display resource definitions.

        json *jsonResDefList = json_CreateObject();
        for (i = 0; i < qpInfo->num_resources; i++) {

            if (!all && has_name_inlist(qpInfo->resource_table[i].name, names, numName)) {
                continue;
            }

            json *jsonResDef = resourcedef_2json(&(qpInfo->resource_table[i]));
            json_AddItemToObject(jsonResDefList, qpInfo->resource_table[i].name, jsonResDef);
        }

        if (0 < json_GetArraySize(jsonResDefList)) {
            char *strJson;
            strJson = json_Print(jsonResDefList);
            if (NULL != strJson) {
                fprintf(stdout, "%s\n", strJson);
                FREEUP(strJson);
            } else {

                perror("malloc");
                json_Delete(jsonResDefList);
                FREEUP(names);

                exit(4);
            }
        } else {
            // nothing in json means there's no queue in the cluster, should not go here.
            fprintf(stderr, "No resource information in the cluster.\n");
        }

        json_Delete(jsonResDefList);
    }
    FREEUP(names);

    if (tOpt) { // display host types.

        json *jsonHostType = json_CreateObject();
        s2j_json_set_array_element(jsonHostType, qpInfo, string, "HOST_TYPES",
                                   host_types, qpInfo->num_types);

        if (0 < json_GetArraySize(jsonHostType)) {
            char *strJson;
            strJson = json_Print(jsonHostType);
            if (NULL != strJson) {
                fprintf(stdout, "%s\n", strJson);
                FREEUP(strJson);
            } else {

                perror("malloc");
                json_Delete(jsonHostType);

                exit(4);
            }
        } else {
            // nothing in json means there's no queue in the cluster, should not go here.
            fprintf(stderr, "No host type in the cluster.\n");
        }

        json_Delete(jsonHostType);
    }

    if (mOpt) { // display host models.

        json *jsonHostModelList = json_CreateObject();
        for (i = 0; i < qpInfo->num_models; ++i) {

            json *jsonHostModel = json_CreateObject();
            json_AddStringToObject(jsonHostModel, "HOST_MODEL", qpInfo->host_models[i]);
            json_AddNumberToObject(jsonHostModel, "CPU_FACTOR", qpInfo->cpu_factor[i]);
            json_AddStringToObject(jsonHostModel, "ARCHITECTURE", qpInfo->host_archs[i]);

            json_AddItemToObject(jsonHostModelList, qpInfo->host_models[i], jsonHostModel);
        }

        if (0 < json_GetArraySize(jsonHostModelList)) {
            char *strJson;
            strJson = json_Print(jsonHostModelList);
            if (NULL != strJson) {
                fprintf(stdout, "%s\n", strJson);
                FREEUP(strJson);
            } else {

                perror("malloc");
                json_Delete(jsonHostModelList);

                exit(4);
            }
        } else {
            // nothing in json means there's no queue in the cluster, should not go here.
            fprintf(stderr, "No host model in the cluster.\n");
        }

        json_Delete(jsonHostModelList);
    }

    exit(0);
} // end function cmd_list_resources

/* ------------------------------------------------
 *
 *  below are static functions used in this file
 *
 * ------------------------------------------------ */

/**
 * @brief       transform resource map structure to json.
 *
 * @param[in]   instance      #1: resource instance to be tranformed
 * @param[in]   resource      #2: resource name
 *
 * @retval      not NULL      #1: transformed json object
 */
static json *
resourcemap_2json(struct QuickPoolResourceMap *instance, char *resource)
{
    json *jsonResMap = json_CreateObject();

    json_AddStringToObject(jsonResMap, "RESOURCE", resource);

    strip_spaces(instance->total_value, FALSE);
    strip_spaces(instance->reserve_value, FALSE);

    s2j_json_set_basic_element(jsonResMap, instance, string, "TOTAL", total_value);
    if (0 != strcmp(instance->reserve_value, "-")) {
        s2j_json_set_basic_element(jsonResMap, instance, string, "RESERVED", reserve_value);
    }

    s2j_json_set_array_element(jsonResMap, instance, string, "LOCATION", hosts, instance->num_hosts);

    return jsonResMap;
} // end function resourcemap_2json

static char *
flag_2string(int flag)
{
    if (flag) {
        return (char *)"yes";
    }

    return (char *)"no";
} // end function flag_2string

static char *
valuetype_2string(value_type_t valtype)
{
    switch (valtype) {
    case TYPE_NUMERIC:
        return (char *)"Numeric";
        break;
    case TYPE_BOOLEAN:
        return (char *)"Boolean";
        break;
    default:
        return (char *)"String";
        break;
    }
} // end function valuetype_2string

static char *
ordertype_2string(enum orderType ordertype)
{
    switch (ordertype) {
    case INCR:
        return (char *)"Inc";
        break;
    case DECR:
        return (char *)"Dec";
        break;
    default:
        return (char *)"N/A";
        break;
    }
} // end function ordertype_2string

/**
 * @brief       transform resource definition structure to json.
 *
 * @param[in]   res           #1: resource definition to be tranformed
 *
 * @retval      not NULL      #1: transformed json object
 */
static json *
resourcedef_2json(struct resource_definition *res)
{
    json *jsonResDef = json_CreateObject();

    s2j_json_set_basic_element(jsonResDef, res, string, "RESOURCE", name);
    json_AddStringToObject(jsonResDef, "TYPE", valuetype_2string(res->value_type));
    json_AddStringToObject(jsonResDef, "ORDER", ordertype_2string(res->order_type));
    s2j_json_set_basic_element(jsonResDef, res, int, "INTERVAL", interval);
    json_AddStringToObject(jsonResDef, "BUILTIN", flag_2string(res->flags & RESOURCE_FLAG_BUILTIN));
    json_AddStringToObject(jsonResDef, "DYNAMIC", flag_2string(res->flags & RESOURCE_FLAG_DYNAMIC));
    json_AddStringToObject(jsonResDef, "RELEASE", flag_2string(res->flags & RESOURCE_FLAG_RELEASE));
    json_AddStringToObject(jsonResDef, "CONSUME", flag_2string(res->flags & RESOURCE_FLAG_CONSUMABLE));

    return jsonResDef;
} // end function resourcedef_2json

static void
resourceinfo_usage(const char *cmd)
{
    fprintf(stdout, "qp resource %s usage: [-h, --help] [-s, --static] [resource_name ...]\n", cmd);

    return;
}

static void
clusterresource_usage(const char *cmd)
{
    fprintf(stdout, "qp cluster %s usage: [-h, --help] [-r, --resource]\n", cmd);
    fprintf(stdout, "\t\t\t   [-m, --model] [-t, --type] [resource_name ...]\n");

    return;
}
